package mmc.server;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.SocketTimeoutException;
import java.util.List;
import java.util.Vector;

import mmc.Constants;
import mmc.cell.ServerCellProvider;
import mmc.networking.Message;
import mmc.networking.TextMessage;
import mmc.server.shell.Shell;

public class Server implements Runnable{

	public static Server instance;
	public static int PORT = 4711;
	public static String BOOT_SCRIPT_PATH = "server.sh";
	public static String CELL_PROVIDER = "mmc.cell3.Cell3Provider";
	
	private ServerCellProvider cellProvider;
	private ServerSocket socket;
	private List<RemoteClient> clients = new Vector<RemoteClient>();
	private Scene scene;
	private Engine engine;
	public boolean quit = false;
	
	public Server(int port){
		instance = this;
		
		try {
			socket = new ServerSocket(port);
			socket.setSoTimeout(1000);
			
			cellProvider = (ServerCellProvider) Class.forName(CELL_PROVIDER).newInstance();
			scene = new Scene(this);
			
			new Thread(this).start();
			new Thread(new Observer(this, scene)).start();			
			engine = new Engine(this);
			
			//set some shell vars for easier debugging
			Shell.interpret("s=Server.instance.getScene()");
			Shell.interpret("cm=s.getCellManager()");
			Shell.interpret("sun=s.getObjects().iterator().next()");
			Shell.interpret("cs=Server.instance.getCellProvider().getCellStorage()");
		} catch (IOException e) {
			System.out.println("Could not open socket!");
			e.printStackTrace();
		} catch (Exception e) {
			System.out.println("Could not create Server: "+e.getMessage());
			e.printStackTrace();
			quit=true;
		}
	}
	
	/**
	 * @param args
	 */
	public static void main(String[] args){
		Constants.IS_SERVER = true;
		
		try {
			Shell.executeScriptFile(new File(BOOT_SCRIPT_PATH));
		} catch (IOException e) {
			System.out.println("WARNING: could not execute "+BOOT_SCRIPT_PATH+": "+e.getMessage());
		}catch(Exception e){
			System.out.println("Exception in Server.init() - Server shutting down:");
			e.printStackTrace(System.out);
			return;
		}
		
		if(args.length==0)
			new Server(PORT);
		else
			new Server(new Integer(args[0]));
	}

	@Override
	public void run() {
		try {
			System.out.println("Server running...");
			while(!quit){
				try{
					clients.add(new RemoteClient(socket.accept(),this));
				}catch(SocketTimeoutException e){}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		engine.shutdown();
		cellProvider.getCellManager().shutdown();
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {}//wait for clients to be disconnected
		System.out.println("Shutting down server...");
		try {
			socket.close();
		} catch (IOException e) {e.printStackTrace();}
	}

	public void clientShutdown(RemoteClient c){
		clients.remove(c);
		scene.removeObject(c.getPlayer());
	}
	
	public Scene getScene(){
		return scene;
	}
	
	public void send(TextMessage msg){
		System.out.println(msg);
		broadcast(msg);
	}
	
	public void broadcast(String text){
		send(new TextMessage(Constants.SERVER_NAME,text,null));
	}
	
	public void broadcast(TextMessage m){
		if(m.msg.startsWith("/")){
			try {
				String ret = Shell.interpret(m.msg.substring(1));
				if(ret.length()!=0)
					send(new TextMessage(Constants.SERVER_NAME,ret,m.from));
			} catch (Exception e) {
				send(new TextMessage(Constants.SERVER_NAME,""+e.getMessage(),m.from)); // the ""+ converts null to "null"
				e.printStackTrace();
			}
		}else{
			boolean sent = false;
			
			for(RemoteClient c : clients){
				if(m.target==null||c.getPlayer().getName().equalsIgnoreCase(m.target)||(!m.from.equalsIgnoreCase("Server")&&c.getPlayer().getName().equalsIgnoreCase(m.from))){
					c.post(m);
					if(c.getPlayer().getName().equalsIgnoreCase(m.target))
						sent = true;
				}
			}
			
			if(m.target!=null && !sent){
				send(new TextMessage(Constants.SERVER_NAME,"could not locate player "+m.target,m.from));
			}
		}
	}
	
	public void broadcast(Message m){
		for(RemoteClient c : clients){
			c.post(m);			
		}	
	}

	public Engine getEngine() {
		return engine;
	}
	
	class Observer implements Runnable
	{
		//private Scene scene;
		private Server server;
		public Observer(Server server, Scene scene)
		{
			this.server = server;
			//this.scene = scene;
		}
		
		@Override
		public void run() {
			BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
			while(!quit)
			{
				try {
					System.out.print(Shell.prompt);
					String input = br.readLine();
					
					if("quit".equals(input)){
						break;
					}else if(input.length()==0){

					}else{
						try {
							System.out.print(Shell.interpret(input));
						} catch (Exception e) {
							e.printStackTrace(System.out);
						}
					}
					
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			
			server.quit = true;
		}
		
	}
	
	public ServerCellProvider getCellProvider(){
		return cellProvider;
	}
}
